我是阿傑,我們在 Day 3 簡單介紹了該如何起手 ECMAScript ,而今天就要進入較為 hardcore 的部分,但其實也沒想像中地這麼可怕,大家可以安心服用。
俗話說射人先射馬,這馬就是 ECMAScript 裡使用的各種術語(terms),理解這些你會發現這世界尚未如此險惡,今天會看以下 4 個名詞:
那我們就開始吧!
我們都知道工程師的人生哲學就是不要一直做重複的事,而 function 便是程式碼中最好的體現 - 重複使用某個區塊的程式碼。
寫規範的人也不例外,ECMAScript 中有非常多重複的操作,撰寫規範的人為了避免不斷地複製貼上,所以 Abstract Operation 便應孕而生,它最大的功能就是將這些需要複用的邏輯、演算法抽離出來,只要這份規範有任何需要它的地方便呼叫它。
會用 呼叫 一詞就是因為它長得跟 function 沒什麼差別,例如 Array.prototype.find
裡用到的 Get(O,Pk)
,(圖 4.1):
是不是似曾相識?它看起來就像物件取值的 getter,並被帶入了 2 個參數 (O
- 物件、Pk
- 屬性的 key) 呼叫,而它也跟普通 funciton 一樣可以 return
一個值給你,你可以試著點進 Get
這個連結看看它做了哪些事。
當然也有些看起來像是 method 的抽象操作,例如 someValue.OperationName(arg1,arg2)
,就好像我們在使用一個 method 一樣!
Note:
抽象操作僅限於 ECMAScript 規範內部使用,並沒有要求實際語言將其實作出來,它只是為了簡化規範而產生的一種凝鍊寫法。
List 是 ECMAScript 內部的一種規範類型 (specification type),也就是說他並不存在於 JavaScript 等實作語言中!當函式呼叫或其他演算法需要一連串的參數 (argument list) 時,它便用來在規範內處理這件事。
List 跟 Array 的形式很像,它是一個有序且可通過索引 (index) 將值取出,例如 arguments[2]
表示 arguments 這個 List 的第 3 個 element,且它有自己的字面語法 (literal),例如: « 1, 2 »
代表一個包含 2 個元素的 List、而一個 new empty List 可以用 « »
來表示。
它目前最常出現的地方應該就是在 arguments
上了,所以暫時把它想像成一般的 Array 也無妨,我們來看看在 Array.prototype.find
的 List 出現在哪 (圖 4.2):
這個 Call
abstrac operation 會呼叫一個 function,並在呼叫時帶入了 3 個參數:
predicate
- 為一個 functionthisArg
- 為指定的 this
value« kValue, ?(k), O »
- 呼叫 predicate 這個 function時所帶入的參數列表 (argumentsList)也就是 Call
帶入了 argumentsList 來呼叫 predicate
這個 function。
在 ECMAScript 中,一個物件的實際語義 (actual semantic) 是透過 internal methods 來定義的...
這句話很短但非常難理解,我試著用一個簡單的例子說明看看 - 大家可能聽過林書豪,但世界上有千百萬個林書豪,如果不透過他的身分證字號 (ID),我們該如何確定他就是我們要找的這個林書豪?其實不難,我們可以透過他的行為模式 - 這個「在 NBA 打球、球技精湛且輸球就吃麥當勞」的林書豪。
所以如果你想成為林書豪並不是改個名字整個容就成了(這個情節似乎在不少電影、漫畫都出現過),你同時還必須擁有跟他一樣的行為模式 - 「在NBA 打球、球技精湛且輸球就吃麥當勞」,但就算如此,別人可能也只會稱你為 小林書豪 XD,因為你們兩個還是不同的個體 (instance)。
如果將林書豪比喻成一個物件,那這些行為就是他的屬性方法 (method),也就是這些屬性方法讓他成為了林書豪。
而在 ECMAScript 裡,不同的物件擁有一系列不同的 internal method,這些 internal method 讓它們在程式運行期間 (runtime) 有著不同的行為,比如一個基本物件會有 [[Get]]
這個 essential internal method,而 function 物件卻有著另一個 [[Call]]
internal method,這個多出來的 [[Call]]
便使它們成為兩個不同種類的物件。
看到這邊,細心如你應該也發現 [[Get]]
看起來就像是 Record 的一個 field,那該如何區分呢?很簡單,就是從它的上下文 (context) 來看,例如:
這個 dot (.
) 前面是一個物件,後面又使用了圓括號 (parentheses) 來呼叫,因此這個 [[Get]]
是一個 internal method。
而這裡的 [[Get]](P,O)
實際語義為 - "讀取 O 物件的 P 屬性", 在 JavaScript 裡就是 o.p
或 o["p"]
。
Note:
Internal method 並非實作語言的一部分,它在 ECMAScript 單純是作為說明而存在的,但實作出來的物件行為必須跟 internal method 所表現的行為一致,至於實現的方法則不在 ECMAScript 的範疇裡。
Internal Slots,一個很難理解的名詞, ECMAScript 利用它來保存資訊並代表物件的內部狀態,但要注意的是 internal slots 並不是物件的屬性而且也不會被繼承 。
在 ECMAScript 中,所有的一般物件 (ordinary object) 都有 [[Prototype]]
這個 internal slot,如果一個物件的 [[Prototype]]
的值不是 null
,就表示它可以存取 [[Prototype]]
的屬性。
而在 JavaScript 中,所有 array instance 的 [[Prototype]]
指向的物件跟 Array.prototype
是一樣的,我們先來看看下面這張圖 (圖 4.3):
從瀏覽器的 console 可以看到這個 emptyArray
只有一個 length
屬性,而大部分的 Array method 都被放在 [[Prototype]]
裡面,聰明的你應該想到怎麼拿到他們了吧?
沒錯,就是直接取用,例如以下( 圖4.4):
雖然 emptyArray
沒有這些 Array method,但它會順著原型鏈 (prototype chain) 往上拿到自己沒有的屬性或 method,很方便吧!而這同時又產生了另一個好處 - 就是我們不需要把所有的屬性跟 method 都定義在 instance 。
最後,我們可以透過瀏覽器提供的 __proto__
屬性及 Object.getPrototypeOf()
這個正規方法來驗證 emptyArray
的 [[Prototype]]
跟 Array.prototype
是否指向同一物件:
Note:
我們無法透過 JavaScript 觀察到 internal slots,但可以透過瀏覽器的 DevTools 將其曝露出來。
我們將在 Day 5 繼續聊聊另外 4 個術語:
?
& !
希望大家可以開心地使用各種咩色,體驗它帶給你的便利,祝大家歸剛沒煩惱。